/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo
*/

#include "simodo/variable/Module_interface.h"
#include "simodo/variable/VariableSetWrapper.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/bormental/DrBormental.h"

#include <cassert>
#include <cmath>

#ifdef CROSS_WIN
// MinGW related workaround
#define BOOST_DLL_FORCE_ALIAS_INSTANTIATION
#endif

#include <boost/dll/alias.hpp>
#include <functional>

using namespace simodo;
using namespace simodo::variable;
using namespace simodo::inout;

namespace
{
    Value _sin(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return sin(std::get<double>(ori.variant()));
    }

    Value _cos(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return cos(std::get<double>(ori.variant()));
    }

    Value _tan(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return tan(std::get<double>(ori.variant()));
    }

    Value _asin(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return asin(std::get<double>(ori.variant()));
    }

    Value _acos(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return acos(std::get<double>(ori.variant()));
    }

    Value _atan(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return atan(std::get<double>(ori.variant()));
    }

    Value _sqrt(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return sqrt(std::get<double>(ori.variant()));
    }

    Value _exp(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return exp(std::get<double>(ori.variant()));
    }

    Value _ln(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return log(std::get<double>(ori.variant()));
    }

    bool isZero(double d) {
        /// \todo PVS Studio err. Нужно проверять на околонулевую дельту (см. проверку на деление на ноль в интерпретаторе)
        /// V590 Consider inspecting the 'd <= 0.0 && 0.0 <= d' expression. 
        /// The expression is excessive or contains a misprint
        return d <= 0.0 && 0.0 <= d;
    }

    Value _atan2ND(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & oriN = args[0].origin();
        assert(oriN.type() == ValueType::Float);
        double num = std::get<double>(oriN.variant());

        const Variable & oriD = args[1].origin();
        assert(oriD.type() == ValueType::Float);
        double denom = std::get<double>(oriD.variant());

        if (isZero(denom)) {
            throw bormental::DrBormental("Module math atan2ND", "Denominator is zero");
        }

        return atan2(num, denom);
    }

    Value _signFloat(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return double (std::signbit(std::get<double>(ori.variant())) ? -1.0 : 1.0);
    }

    Value _signInt(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Int);
        return int64_t (std::signbit(std::get<int64_t>(ori.variant())) ? -1 : 1);
    }

    Value _asinND(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & oriN = args[0].origin();
        assert(oriN.type() == ValueType::Float);
        double num = std::get<double>(oriN.variant());

        const Variable & oriD = args[1].origin();
        assert(oriD.type() == ValueType::Float);
        double denom = std::get<double>(oriD.variant());

        if (isZero(denom)) {
            throw bormental::DrBormental("Module math asinND", "Denominator is zero");
        }

        return asin(num / denom);
    }

    Value _acosND(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & oriN = args[0].origin();
        assert(oriN.type() == ValueType::Float);
        double num = std::get<double>(oriN.variant());

        const Variable & oriD = args[1].origin();
        assert(oriD.type() == ValueType::Float);
        double denom = std::get<double>(oriD.variant());

        if (isZero(denom)) {
            throw bormental::DrBormental("Module math acosND", "Denominator is zero");
        }

        return acos(num / denom);
    }

    Value _atanND(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & oriN = args[0].origin();
        assert(oriN.type() == ValueType::Float);
        double num = std::get<double>(oriN.variant());

        const Variable & oriD = args[1].origin();
        assert(oriD.type() == ValueType::Float);
        double denom = std::get<double>(oriD.variant());

        if (isZero(denom)) {
            throw bormental::DrBormental("Module math atanND", "Denominator is zero");
        }

        return atan(num / denom);
    }

    Value _clampReal(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & oriL = args[0].origin();
        assert(oriL.type() == ValueType::Float);
        double left = std::get<double>(oriL.variant());

        const Variable & oriV = args[1].origin();
        assert(oriV.type() == ValueType::Float);
        double value = std::get<double>(oriV.variant());

        const Variable & oriR = args[2].origin();
        assert(oriR.type() == ValueType::Float);
        double right = std::get<double>(oriR.variant());

        if (left > right) {
            throw bormental::DrBormental("Module math clampReal", "Left boundary is greater than right one");
        }

        return std::clamp(value, left, right);
    }

    Value _clampInt(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & oriL = args[0].origin();
        assert(oriL.type() == ValueType::Int);
        int64_t left = std::get<int64_t>(oriL.variant());

        const Variable & oriV = args[1].origin();
        assert(oriV.type() == ValueType::Int);
        int64_t value = std::get<int64_t>(oriV.variant());

        const Variable & oriR = args[2].origin();
        assert(oriR.type() == ValueType::Int);
        int64_t right = std::get<int64_t>(oriR.variant());

        if (left > right) {
            throw bormental::DrBormental("Module math clampReal", "Left boundary is greater than right one");
        }

        return std::clamp(value, left, right);
    }

    Value _absReal(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Float);
        return abs(std::get<double>(ori.variant()));
    }

    Value _absInt(Module_interface * , const VariableSetWrapper & args)
    {
        const Variable & ori = args[0].origin();
        assert(ori.type() == ValueType::Int);
        return abs(std::get<int64_t>(ori.variant()));
    }
}

/*!
    * \brief Получение ссылки на глобальное пространство имён математических операций и констант
    */
class ModuleHost_math : public Module_interface
{
public:
    virtual version_t version() const override { return lib_version(); }

    virtual Object instantiate(std::shared_ptr<Module_interface> module_host) override {
        return {{
            {u"pi", M_PI},
            {u"sin", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_host, _sin}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"cos", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _cos}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"tan", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _tan}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"asin", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _asin}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"acos", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _acos}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"atan", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _atan}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"sqrt", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _sqrt}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"exp", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _exp}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"ln", {ValueType::Function, Object {{
                {u"@", ExternalFunction {{module_host}, _ln}},
                {{}, ValueType::Float},
                {u"_value_", ValueType::Float},
            }}}},
            {u"atan2ND", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _atan2ND}},
                    {{}, ValueType::Float},
                    {u"_y_", ValueType::Float},
                    {u"_x_", ValueType::Float},
            }}}},
            {u"signFloat", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _signFloat}},
                    {{}, ValueType::Float},
                    {u"_value_", ValueType::Float},
            }}}},
            {u"signInt", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _signInt}},
                    {{}, ValueType::Int},
                    {u"_value_", ValueType::Int},
            }}}},
            {u"asinND", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _asinND}},
                    {{}, ValueType::Float},
                    {u"_n_", ValueType::Float},
                    {u"_d_", ValueType::Float},
            }}}},
            {u"acosND", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _acosND}},
                    {{}, ValueType::Float},
                    {u"_n_", ValueType::Float},
                    {u"_d_", ValueType::Float},
            }}}},
            {u"atanND", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _atanND}},
                    {{}, ValueType::Float},
                    {u"_n_", ValueType::Float},
                    {u"_d_", ValueType::Float},
            }}}},
            {u"clampReal", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _clampReal}},
                    {{}, ValueType::Float},
                    {u"_left_", ValueType::Float},
                    {u"_value_", ValueType::Float},
                    {u"_right_", ValueType::Float},
            }}}},
            {u"clampInt", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _clampInt}},
                    {{}, ValueType::Int},
                    {u"_left_", ValueType::Int},
                    {u"_value_", ValueType::Int},
                    {u"_right_", ValueType::Int},
            }}}},
            {u"absReal", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _absReal}},
                    {{}, ValueType::Float},
                    {u"_value_", ValueType::Float},
            }}}},
            {u"absInt", {ValueType::Function, Object {{
                    {u"@", ExternalFunction {{module_host}, _absInt}},
                    {{}, ValueType::Int},
                    {u"_value_", ValueType::Int},
            }}}},
        }};
    }

    // Factory method
    static std::shared_ptr<Module_interface> create() {
        return std::make_shared<ModuleHost_math>();
    }
};

BOOST_DLL_ALIAS(
    ModuleHost_math::create,    // <-- this function is exported with...
    create_simodo_module                              // <-- ...this alias name
)

